﻿using Gargoyle.Roton;
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;

namespace Gargoyle.ZZT
{
	/// <summary>
	/// Contains Act code for each element.
	/// </summary>
    static class Act
    {
		/// <summary>
		/// Act code for a bear.
		/// </summary>
        public static void Bear(Game g, int index)
        {
            Thing thing = g.Things[index];
            Thing player = g.Things[0];
            Vector vector = new Vector();

            if ((player.Location.X == thing.Location.X) || (8 - thing.P1 < g.Difference(player.Location.Y, thing.Location.Y)))
            {
                if (8 - thing.P1 < g.Difference(player.Location.X, thing.Location.X))
                    vector.CopyFrom(new Vector(0, 0));
                else
                    vector.CopyFrom(new Vector(0, (Int16)g.Polarity(player.Location.Y - thing.Location.Y)));
            }
            else
            {
                vector.CopyFrom(new Vector((Int16)g.Polarity(player.Location.X - thing.Location.X), 0));
            }

            Location target = thing.Location.Add(vector);
            Element targetElement = g.GetElementAt(target);
            if (targetElement.Floor)
            {
                g.MoveThing(index, target);
            }
            else if ((targetElement.Id == ElementID.Player) || (targetElement.Id == ElementID.Breakable))
            {
                g.Attack(index, target);
            }
        }

		/// <summary>
		/// Act code for a blink wall.
		/// </summary>
		public static void BlinkWall(Game g, int index)
        {
            Thing thing = g.Things[index];
            byte color;

            if (thing.P3 == 0)
                thing.P3 = (byte)(thing.P1 + 1);

            if (thing.P3 == 1)
            {
                thing.P3 = (byte)((thing.P2 * 2) + 1);

                bool erasedRay = false;
                Location target = thing.Location.Add(thing.Vector);
                Element emptyElement = g.Elements[ElementID.Empty];
                byte rayElement;
                Tile rayTile;

                if (thing.XStep == 0)
                    rayElement = g.Elements.IndexOf(ElementID.BlinkRayV);
                else
                    rayElement = g.Elements.IndexOf(ElementID.BlinkRayH);

                color = g.GetColorAt(thing.Location);
                rayTile = new Tile(rayElement, color);

                while (g.GetTileAt(target) == rayTile)
                {
                    g.SetElementAt(target, emptyElement);
                    g.UpdateAt(target);
                    target = target.Add(thing.Vector);
                    erasedRay = true;
                }

                if (!erasedRay)
                {
                    bool blocked = false;
                    
                    do
                    {
                        if (g.GetElementAt(target).Destructible)
                        {
                            g.DestroyAt(target);
                        }
                        if (g.GetElementAt(target).Id == ElementID.Player)
                        {
                            int playerIndex = g.GetThingIndexAt(target);
                            Vector testVector;

                            if (thing.YStep == 0)
                            {
                                testVector = new Vector(0, 1);
                                if (g.GetElementAt(target.Subtract(testVector)).Id == ElementID.Empty)
                                {
                                    g.MoveThing(playerIndex, target.Subtract(testVector));
                                }
                                else if (g.GetElementAt(target.Add(testVector)).Id == ElementID.Empty)
                                {
                                    g.MoveThing(playerIndex, target.Add(testVector));
                                }
                            }
                            else
                            {
                                testVector = new Vector(1, 0);
                                if (g.GetElementAt(target.Add(testVector)).Id == ElementID.Empty)
                                {
                                    g.MoveThing(playerIndex, target.Add(testVector));
                                }
                                else if (g.GetElementAt(target.Subtract(testVector)).Id == ElementID.Empty)
                                {
                                    // this still needs to say Add, it is an actual bug in ZZT.
                                    g.MoveThing(playerIndex, target.Add(testVector));
                                }
                            }
                            if (g.GetElementAt(target).Id == ElementID.Player)
                            {
                                while (g.Info.Health > 0)
                                {
                                    g.HarmThing(0);
                                }
                                blocked = true;
                            }
                        }
                        if (g.GetElementAt(target).Id == ElementID.Empty)
                        {
                            g.SetTileAt(target, rayTile);
                            g.UpdateAt(target);
                        }
                        else
                        {
                            blocked = true;
                        }
                        target = target.Add(thing.Vector);
                    } while (!blocked);
                }
            }
            else
            {
                thing.P3--;
            }
        }

		/// <summary>
		/// Act code for a bomb.
		/// </summary>
		public static void Bomb(Game g, int index)
        {
            Thing thing = g.Things[index];

            if (thing.P1 > 0)
            {
                thing.P1--;
                g.UpdateAt(thing.Location);
                if (thing.P1 == 1)
                {
                    g.PlaySound(1, g.Sounds.BombExplode);
                    g.Radius(thing.Location, Game.RadiusType.Explode);
                }
                else if (thing.P1 == 0)
                {
                    Location location = thing.Location;
                    g.RemoveThing(index);
                    g.Radius(location, Game.RadiusType.Cleanup);
                }
                else
                {
                    if (thing.P1 % 2 == 0)
                    {
                        g.PlaySound(1, g.Sounds.BombTock);
                    }
                    else
                    {
                        g.PlaySound(1, g.Sounds.BombTick);
                    }
                }
            }
        }

		/// <summary>
		/// Act code for a bullet.
		/// </summary>
		public static void Bullet(Game g, int index)
        {
            Thing thing = g.Things[index];
            bool ricochet = true;

            while (true)
            {
                Location target = thing.Location.Add(thing.Vector);
                Element element = g.GetElementAt(target);

                if (element.Floor || (element.Id == ElementID.Water))
                {
                    g.MoveThing(index, target);
                    break;
                }

                if (ricochet && (element.Id == ElementID.Ricochet))
                {
                    g.PlaySound(1, g.Sounds.Ricochet);
                    thing.Vector.CopyFrom(thing.Vector.Opposite());
                    ricochet = false;
                    continue;
                }

                if (element.Id != ElementID.Breakable)
                {
                    if (!element.Destructible || ((element.Id != ElementID.Player) && (thing.P1 != 0)))
                    {
                        if (ricochet && (g.GetElementAt(thing.Location.Add(thing.Vector.CounterClockwise())).Id == ElementID.Ricochet))
                        {
                            g.PlaySound(1, g.Sounds.Ricochet);
                            thing.Vector.CopyFrom(thing.Vector.Clockwise());
                            ricochet = false;
                            continue;
                        }
                        else if (ricochet && (g.GetElementAt(thing.Location.Add(thing.Vector.Clockwise())).Id == ElementID.Ricochet))
                        {
                            g.PlaySound(1, g.Sounds.Ricochet);
                            thing.Vector.CopyFrom(thing.Vector.CounterClockwise());
                            ricochet = false;
                            continue;
                        }
                        else
                        {
                            g.RemoveThing(index);
                            g.State.ActIndex--;
                            if (element.Id == ElementID.Object)
                            {
                                g.SendLabel(-g.GetThingIndexAt(target), g.Parameters.ShotLabel, false);
                            }
                            break;
                        }
                    }
                }

                if (element.Points > 0)
                {
                    g.Info.Score += element.Points;
                    g.UpdateStatus();
                }
                g.Attack(index, target);
                break;
            }
        }


		/// <summary>
		/// Act code for a clockwise conveyor.
		/// </summary>
		public static void Clockwise(Game g, int index)
        {
            Thing thing = g.Things[index];
            g.UpdateAt(thing.Location);
            ConveyorMove(g, thing.Location, 1);
        }

		/// <summary>
		/// Common conveyor code for conveyors.
		/// </summary>
		/// <param name="location">The conveyor's location.</param>
		/// <param name="direction">Use 1 for clockwise and -1 for counter-clockwise.</param>
        private static void ConveyorMove(Game g, Location location, int direction)
        {
            int beginIndex;
            int endIndex;

            Tile[] surrounding = new Tile[8];

            if (direction == 1)
            {
                beginIndex = 0;
                endIndex = 8;
            }
            else
            {
                beginIndex = 7;
                endIndex = -1;
            }

            bool pushable = true;
            for (int i = beginIndex; i != endIndex; i += direction)
            {
                surrounding[i] = g.GetTileAt(location.Add(g.Parameters.ConveyorVectors[i]));
                Element element = g.Elements[surrounding[i].Element];
                if (element.Id == ElementID.Empty)
                    pushable = true;
                else if (!element.Pushable)
                    pushable = false;
            }

            for (int i = beginIndex; i != endIndex; i += direction)
            {
                Element element = g.Elements[surrounding[i].Element];

                if (pushable)
                {
                    if (element.Pushable)
                    {
                        Location source = location.Add(g.Parameters.ConveyorVectors[i]);
                        Location target = location.Add(g.Parameters.ConveyorVectors[(i + 8 - direction) % 8]);
                        if (element.Cycle > -1)
                        {
                            Tile tile = g.GetTileAt(source);
                            int index = g.GetThingIndexAt(source);
                            g.SetTileAt(source, surrounding[i]);
                            g.SetElementAt(target, g.Elements[ElementID.Empty]);
                            g.MoveThing(index, target);
                            g.SetTileAt(source, tile);
                        }
                        else
                        {
                            g.SetTileAt(target, surrounding[i]);
                            g.UpdateAt(target);
                        }
                        if (!g.Elements[surrounding[(i + 8 + direction) % 8].Element].Pushable)
                        {
                            g.SetElementAt(source, g.Elements[ElementID.Empty]);
                            g.UpdateAt(source);
                        }
                    }
                    else
                    {
                        pushable = false;
                    }
                }
                else
                {
                    if (element.Id == ElementID.Empty)
                        pushable = true;
                    else if (!element.Pushable)
                        pushable = false;
                }
            }

        }

		/// <summary>
		/// Act code for a counter-clockwise conveyor.
		/// </summary>
        public static void Counter(Game g, int index)
        {
            Thing thing = g.Things[index];
            g.UpdateAt(thing.Location);
            ConveyorMove(g, thing.Location, -1);
        }

		/// <summary>
		/// Act code for a dragon pup.
		/// </summary>
		public static void DragonPup(Game g, int index)
        {
            g.UpdateAt(g.Things[index].Location);
        }

		/// <summary>
		/// Act code for a duplicator.
		/// </summary>
		public static void Duplicator(Game g, int index)
        {
            Thing thing = g.Things[index];
            Location source = thing.Location.Add(thing.Vector);
            Location target = thing.Location.Subtract(thing.Vector);

            if (thing.P1 > 4)
            {
                if (g.GetElementAt(target).Id == ElementID.Player)
                {
                    g.Interact(source, 0, g.State.KeyVector);
                }
                else
                {
                    if (g.GetElementAt(target).Id != ElementID.Empty)
                    {
                        g.Push(target, thing.Vector.Opposite());
                    }
                    if (g.GetElementAt(target).Id == ElementID.Empty)
                    {
                        int sourceIndex = g.GetThingIndexAt(source);
                        if (sourceIndex > 0)
                        {
                            if (g.Things.Count < g.Parameters.MaxThings)
                            {
                                Tile sourceTile = g.GetTileAt(source);
                                Element sourceElement = g.Elements[sourceTile.Element];
                                g.SpawnThing(target, sourceElement, sourceTile.Color, sourceElement.Cycle, g.Things[sourceIndex]);
                                g.UpdateAt(target);
                            }
                        }
                        else if (sourceIndex != 0)
                        {
                            g.SetTileAt(target, g.GetTileAt(source));
                            g.UpdateAt(target);
                        }
                        g.PlaySound(3, g.Sounds.Duplicate);
                    }
                    else
                    {
                        g.PlaySound(3, g.Sounds.DuplicateFail);
                    }
                }
                thing.P1 = 0;
            }
            else
            {
                thing.P1++;
            }
            
            g.UpdateAt(thing.Location);
            thing.Cycle = (Int16)((9 - thing.P2) * 3);
        }

		/// <summary>
		/// Act code for generic enemy movement. Used by Lions and Tigers.
		/// </summary>
		private static void EnemyMoveGeneric(Game g, int index, Vector vector)
        {
            Location target = g.Things[index].Location.Add(vector);
            Element targetElement = g.GetElementAt(target);
            if (targetElement.Floor)
            {
                g.MoveThing(index, target);
            }
            else if ((targetElement.Id == ElementID.Player) || (targetElement.Id == ElementID.Breakable))
            {
                g.Attack(index, target);
            }
        }

		/// <summary>
		/// Act code for a centipede head.
		/// </summary>
		public static void Head(Game g, int index)
        {
            Thing player = g.Things[0];
            Thing thing = g.Things[index];

            if (player.X == thing.X && thing.P1 > g.RandomNumber(10))
            {
                thing.Vector.CopyFrom(g.Seek(thing.Location));
            }
            else if (player.Y == thing.Y && thing.P1 > g.RandomNumber(10))
            {
                thing.Vector.CopyFrom(g.Seek(thing.Location));
            }
            else if (thing.Vector.IsIdle || thing.P2 > (g.RandomNumber(10) << 2))
            {
                thing.Vector.CopyFrom(g.Rnd());
            }

            if (!thing.Vector.IsIdle)
            {
                Vector vector = thing.Vector.Clone();
                Element element;

                element = g.GetElementAt(thing.Location.Add(vector));
                if (!element.Floor && element.Id != ElementID.Player)
                {
                    thing.Vector.CopyFrom(g.RndP(vector));
                    element = g.GetElementAt(thing.Location.Add(thing.Vector));
                    if (!element.Floor && element.Id != ElementID.Player)
                    {
                        thing.Vector.CopyFrom(thing.Vector.Opposite());
                        element = g.GetElementAt(thing.Location.Add(thing.Vector));
                        if (!element.Floor && element.Id != ElementID.Player)
                        {
                            thing.Vector.CopyFrom(vector.Opposite());
                            element = g.GetElementAt(thing.Location.Add(thing.Vector));
                            if (!element.Floor && element.Id != ElementID.Player)
                            {
                                thing.Vector.CopyFrom(Vector.Idle());
                            }
                        }
                    }
                }
            }

            if (thing.Vector.IsIdle)
            {
                g.SetElementAt(thing.Location, g.Elements[ElementID.Segment]);
                g.UpdateAt(thing.Location);
                while (true)
                {
                    Thing segment = g.Things[index];
                    Int16 i = segment.Follower;
                    segment.Follower = segment.Leader;
                    segment.Leader = i;
                    if (i > 0)
                        index = i;
                    else
                        break;
                }
                thing = g.Things[index];
                g.SetElementAt(thing.Location, g.Elements[ElementID.Head]);
                g.UpdateAt(thing.Location);
            }
            else
            {
                Location target = thing.Location.Add(thing.Vector);

                if (g.GetElementAt(target).Id == ElementID.Player)
                {
                    if (thing.Follower > 0)
                    {
                        Thing follower = g.Things[thing.Follower];
                        g.SetElementAt(follower.Location, g.Elements[ElementID.Head]);
                        follower.Leader = -1;
                        g.UpdateAt(follower.Location);
                    }
                    thing.Follower = -1;
                    thing.Leader = -1;
                    g.Attack(index, target);
                }
                else
                {
                    g.MoveThing(index, target);
                    do
                    {
                        thing = g.Things[index];

                        Location origin = thing.Location.Subtract(thing.Vector);
                        Vector vector = thing.Vector.Clone();

                        if (thing.Follower < 0)
                        {
                            if (g.GetElementAt(origin.Subtract(vector)).Id == ElementID.Segment && g.GetThingAt(origin.Subtract(vector)).Leader <= 0)
                            {
                                thing.Follower = (Int16)g.GetThingIndexAt(origin.Subtract(vector));
                            }
                            else if (g.GetElementAt(origin.Subtract(vector.Swap())).Id == ElementID.Segment && g.GetThingAt(origin.Subtract(vector.Swap())).Leader <= 0)
                            {
                                thing.Follower = (Int16)g.GetThingIndexAt(origin.Subtract(vector.Swap()));
                            }
                            else if (g.GetElementAt(origin.Add(vector.Swap())).Id == ElementID.Segment && g.GetThingAt(origin.Add(vector.Swap())).Leader <= 0)
                            {
                                thing.Follower = (Int16)g.GetThingIndexAt(origin.Add(vector.Swap()));
                            }
                            else
                            {
                                thing.Follower = -1;
                            }
                        }

                        if (thing.Follower > 0)
                        {
                            Thing follower = g.Things[thing.Follower];
                            follower.Leader = (Int16)index;
                            follower.P1 = thing.P1;
                            follower.P2 = thing.P2;
                            follower.Vector.CopyFrom(new Vector((Int16)(origin.X - follower.X), (Int16)(origin.Y - follower.Y)));
                            g.MoveThing(thing.Follower, origin);
                        }

                        index = thing.Follower;
                    } while (index > 0);
                }
            }

        }

		/// <summary>
		/// Act code for a lion.
		/// </summary>
		public static void Lion(Game g, int index)
        {
            Thing thing = g.Things[index];
            Vector vector = new Vector();

            if (thing.P1 >= g.RandomNumber(10))
            {
                vector.CopyFrom(g.Seek(thing.Location));
            }
            else
            {
                vector.CopyFrom(g.Rnd());
            }

            EnemyMoveGeneric(g, index, vector);
        }

		/// <summary>
		/// Act code for a messenger.
		/// </summary>
		/// 
		public static void Messenger(Game g, int index)
        {
			// todo: actually implement messenger as it is in ZZT
			// in regards to displaying and updating message on the screen
            Thing thing = g.Things[index];

            if (thing.Location.X == 0)
            {
                thing.P2--;
                if (thing.P2 <= 0)
                {
                    g.ClearMessage();
                    g.RemoveThing(index);
                    g.State.ActIndex--;
                }
            }
        }

		/// <summary>
		/// Act code for a monitor.
		/// </summary>
        public static void Monitor(Game g, int index)
        {
            if (g.State.KeyPressed != 0)
                g.State.BreakGameLoop = true;

            if (g.Parameters.RiverEnabled)
                MoveRiver(g, index);
        }

		/// <summary>
		/// If directional water is under the player, move it accordingly.
		/// </summary>
		private static void MoveRiver(Game g, int index)
        {
            Thing thing = g.Things[index];
            Vector vector = new Vector(0, 0);

            switch (g.Elements[thing.UnderTile.Element].Id)
            {
                case ElementID.WaterN:
                    vector.Y = -1;
                    break;
                case ElementID.WaterS:
                    vector.Y = 1;
                    break;
                case ElementID.WaterW:
                    vector.X = -1;
                    break;
                case ElementID.WaterE:
                    vector.X = 1;
                    break;
            }

            if (!vector.IsIdle)
            {
                Location target = thing.Location.Add(vector);
                if (index == 0)
                    g.Interact(target, 0, vector);
                if (g.GetElementAt(target).Floor)
                    g.MoveThing(index, target);
            }
        }

		/// <summary>
		/// Act code for an object.
		/// </summary>
		public static void Object(Game g, int index)
        {
            Thing thing = g.Things[index];
            bool execute;

            if (thing.Code == null)
                thing.Instruction = -1;

            execute = (thing.Code != null);
            execute &= (thing.Instruction >= 0 || g.OOPState != null);
            execute &= (!g.Parameters.MultiMovement || thing.P2 == 0);

            if (execute)
            {
                ByteString name = null;

                if (g.OOPState == null)
                    name = g.Parameters.DefaultObjectName;
                else
                    name = g.OOPState.Name;

                g.ExecuteOOP(index, thing.InstructionReference, name);
            }

            thing = g.Things[index];
            if (g.OOPState == null && thing.Location.X > 0)
            {
                Location target = thing.Location.Add(thing.Vector);

                if (g.Parameters.MultiMovement)
                {
                    if (!thing.Vector.IsIdle)
                    {
                        if (!g.GetElementAt(target).Floor)
                        {
                            g.Push(target, thing.Vector);
                        }
                        if (g.GetElementAt(target).Floor)
                        {
                            g.MoveThing(index, target);
                            if (thing.P2 > 0)
                            {
                                thing.P2--;
                                if (thing.P2 == 0)
                                {
                                    thing.Vector.CopyFrom(Vector.Idle());
                                }
                            }
                        }
                        else if (thing.P2 == 0)
                        {
                            g.SendLabel(-index, g.Parameters.ThudLabel, false);
                        }
                    }
                    else if (thing.P2 > 0)
                    {
                        thing.P2--;
                    }
                }
                else
                {
                    if (!thing.Vector.IsIdle)
                    {
                        if (g.GetElementAt(target).Floor)
                        {
                            g.MoveThing(index, target);
                        }
                        else
                        {
                            g.SendLabel(-index, g.Parameters.ThudLabel, false);
                        }
                    }
                }
            }
        }

		/// <summary>
		/// Act code for a pairer.
		/// </summary>
		public static void Pairer(Game g, int index)
        {
            // there is actually no code for pairers
        }

		/// <summary>
		/// Act code for a player.
		/// </summary>
		public static void Player(Game g, int index)
        {
            Thing thing = g.Things[index];
            Thing player = g.Things[0];
            Element playerElement = g.Elements[ElementID.Player];
            Info info = g.Info;
            State state = g.State;
            Parameters parameters = g.Parameters;
            Board board = g.CurrentBoard;
            ThingCollection things = g.Things;

            if (info.EnergyCycles > 0)
            {
                if (playerElement.DefaultChar == 2)
                    playerElement.DefaultChar = 1;
                else
                    playerElement.DefaultChar = 2;

                if (state.Tick % 2 == 0)
                    g.SetColorAt(thing.Location, (byte)((((state.Tick % 7) + 1) << 4) | 0x0F));
                else
                    g.SetColorAt(thing.Location, 0x0F);
            }
            else
            {
                playerElement.DefaultChar = 2;
                if (parameters.ForcePlayerColor)
                    g.SetColorAt(thing.Location, playerElement.DefaultColor);
            }
            g.UpdateAt(thing.Location);

            if (state.KeyPressed == Keys.Space)
            {
                state.KeyVector.CopyFrom(state.KeyLastVector);
                state.KeyShift = true;
            }

            if (info.Health <= 0)
            {
                state.KeyVector.CopyFrom(new Vector(0, 0));
                if (g.GetThingIndexAt(new Location(0, 0)) == -1)
                {
                    g.SetMessage(0x7D00, parameters.GameOverMessage);
                }
                state.WaitTime = 1;
                state.GameOver = true;
            }

            if ((state.KeyShift) || (state.KeyPressed == Keys.Space))
            {
                if (!state.KeyVector.IsIdle)
                {
                    if (board.Shots > 0)
                    {
                        if (info.Ammo > 0)
                        {
                            int bulletCount = 0;
                            for (int i = 0; i < things.Count; i++)
                            {
                                if (g.GetElementAt(things[i].Location).Id == ElementID.Bullet)
                                {
                                    if (things[i].P1 == 0)
                                    {
                                        bulletCount++;
                                    }
                                }
                            }
                            if (bulletCount < board.Shots)
                            {
                                if (g.SpawnProjectile(g.Elements[ElementID.Bullet], thing.Location, state.KeyVector, false))
                                {
                                    info.Ammo--;
                                    g.UpdateStatus();
                                    g.PlaySound(2, g.Sounds.Shoot);
                                }
                            }
                        }
                        else
                        {
                            if (state.AlertNoAmmo)
                            {
                                g.SetMessage(0xC8, parameters.NoAmmoMessage);
                                state.AlertNoAmmo = false;
                            }
                        }
                    }
                    else
                    {
                        if (state.AlertCantShoot)
                        {
                            g.SetMessage(0xC8, parameters.CantShootMessage);
                            state.AlertCantShoot = false;
                        }
                    }
                }
            }
            else
            {
                if (!state.KeyVector.IsIdle)
                {
                    Location target = thing.Location.Add(state.KeyVector);
                    g.Interact(target, 0, state.KeyVector);
                    if (!state.KeyVector.IsIdle)
                    {
                        target = thing.Location.Add(state.KeyVector);
                        if (g.GetElementAt(target).Floor)
                        {
                            g.MoveThing(0, target);
                            if (!state.Quiet)
                            {
                                g.PlayStepSound();
                            }
                        }
                    }
                }
            }

            switch (state.KeyPressed)
            {
                case Keys.T:
                    if (parameters.TorchesEnabled && (info.TorchCycles <= 0))
                    {
                        if (info.Torches > 0)
                        {
                            if (board.Dark != 0)
                            {
                                info.Torches--;
                                info.TorchCycles = 200;
                                g.UpdateStatus();
                                g.Radius(thing.Location, Game.RadiusType.Update);
                            }
                            else
                            {
                                if (state.AlertNotDark)
                                {
                                    g.SetMessage(0xC8, parameters.NotDarkMessage);
                                    state.AlertNotDark = false;
                                }
                            }
                        }
                        else
                        {
                            if (state.AlertNoTorch)
                            {
                                g.SetMessage(0xC8, parameters.NoTorchMessage);
                                state.AlertNoTorch = false;
                            }
                        }
                    }
                    break;
                case Keys.Escape:
                case Keys.Q:
                    state.BreakGameLoop = true;
                    g.ShowEndGame();
                    break;
                case Keys.S:
                    g.ShowSaveGame();
                    state.Paused = true;
                    break;
                case Keys.R:
                    g.ShowRestoreGame();
                    break;
                case Keys.P:
                    if (info.Health > 0)
                    {
                        state.Paused = true;
                        g.UpdateStatus();
                    }
                    break;
                case Keys.B:
                    state.Quiet = !state.Quiet;
                    g.ClearSound();
                    g.UpdateStatus();
                    state.KeyPressed = Keys.Space;
                    state.SoundPlaying = false;
                    state.SoundTicks = 0;
                    state.SoundPriority = 0;
                    break;
                case Keys.H:
                    if (parameters.HintsEnabled)
                    {
                        g.BroadcastLabel(0, g.Parameters.HintLabel);
                    }
                    else
                    {
                        g.ShowHelp("GAME");
                    }
                    break;
                case Keys.F:
                    // execute help ORDER (shareware only)
                    break;
                case Keys.OemQuestion:
                    if (state.KeyShift)
                    {
                        g.ShowCheat();
                    }
                    break;
                default:
                    break;
            }

            if (info.TorchCycles > 0)
            {
                info.TorchCycles--;
                if (info.TorchCycles <= 0)
                {
                    g.Radius(thing.Location, Game.RadiusType.Update);
                    g.PlaySound(3, g.Sounds.TorchOut);
                }
                if ((info.TorchCycles % 40) == 0)
                {
                    g.UpdateStatus();
                }
            }

            if (info.EnergyCycles > 0)
            {
                info.EnergyCycles--;
                if (info.EnergyCycles == 10)
                {
                    g.PlaySound(9, g.Sounds.EnergyOut);
                }
                else if (info.EnergyCycles == 0)
                {
                    g.SetColorAt(thing.Location, g.Elements[ElementID.Player].DefaultColor);
                    g.UpdateAt(thing.Location);
                }
            }

            if ((board.TimeLimit > 0) && (info.Health > 0))
            {
                if (state.TimeOnBoard >= parameters.TicksPerSecond)
                {
                    info.TimePassed++;
                    state.TimeOnBoard -= parameters.TicksPerSecond;
                    if (board.TimeLimit - info.TimePassed == 10)
                    {
                        g.SetMessage(0xC8, parameters.NoTimeMessage);
                        g.PlaySound(3, g.Sounds.TimeLow);
                    }
                    else if (board.TimeLimit == info.TimePassed)
                    {
                        g.HarmThing(0);
                    }
                    g.UpdateStatus();
                }
            }

            if (parameters.RiverEnabled)
            {
                MoveRiver(g, index);
            }
        }

		/// <summary>
		/// Act code for a pusher.
		/// </summary>
		public static void Pusher(Game g, int index)
        {
            Thing thing = g.Things[index];
            Location location = thing.Location;
            Location target = thing.Location.Add(thing.Vector);
            Element element = g.GetElementAt(target);

            if (!element.Floor)
            {
                g.Push(target, thing.Vector);
                index = g.GetThingIndexAt(location);
                thing = g.Things[index];
                element = g.GetElementAt(target);
            }

            if (element.Floor)
            {
                g.MoveThing(index, target);
                g.PlaySound(2, g.Sounds.Push);
                Location behind = location.Subtract(thing.Vector);
                if (g.GetElementAt(behind).Id == ElementID.Pusher)
                {
                    Thing behindThing = g.GetThingAt(behind);
                    if (behindThing.Vector == thing.Vector)
                    {
                        Pusher(g, g.GetThingIndexAt(behind));
                    }
                }
            }
        }

		/// <summary>
		/// Act code for a roton.
		/// </summary>
		public static void Roton(Game g, int index)
        {
            Thing thing = g.Things[index];

            thing.P3--;
            if (thing.P3 < (-thing.P2 % 10))
            {
                thing.P3 = (byte)((thing.P2 * 10) + g.RandomNumber(10));
            }
            
            thing.Vector.CopyFrom(g.Seek(thing.Location));
            if (thing.P1 <= g.RandomNumber(10))
            {
                Int16 i;
                i = thing.XStep;
                thing.XStep = (Int16)(-g.Polarity(thing.P2) * thing.YStep);
                thing.YStep = (Int16)(g.Polarity(thing.P2) * i);
            }

            Location target = thing.Location.Add(thing.Vector);
            Element element = g.GetElementAt(target);
            if (element.Floor)
            {
                g.MoveThing(index, target);
            }
            else if (element.Id == ElementID.Player)
            {
                g.Attack(index, target);
            }
        }

		/// <summary>
		/// Act code for a ruffian.
		/// </summary>
		public static void Ruffian(Game g, int index)
        {
            Thing thing = g.Things[index];

            if (!thing.Vector.IsIdle)
            {
                Thing player = g.Things[0];
                if ((player.Location.X == thing.Location.X) || (player.Location.Y == thing.Location.Y))
                {
                    if (thing.P1 >= g.RandomNumber(9))
                    {
                        thing.Vector.CopyFrom(g.Seek(thing.Location));
                    }
                }
                
                Location target = thing.Location.Add(thing.Vector);
                Element element = g.GetElementAt(target);
                if (element.Id == ElementID.Player)
                {
                    g.Attack(index, target);
                }
                else if (element.Floor)
                {
                    g.MoveThing(index, target);
                    if (thing.P2 + 8 <= g.RandomNumber(17))
                    {
                        thing.Vector.CopyFrom(Vector.Idle());
                    }
                }
                else
                {
                    thing.Vector.CopyFrom(Vector.Idle());
                }
            }
            else
            {
                if (thing.P2 + 8 <= g.RandomNumber(17))
                {
                    if (thing.P1 >= g.RandomNumber(9))
                        thing.Vector.CopyFrom(g.Seek(thing.Location));
                    else
                        thing.Vector.CopyFrom(g.Rnd());
                }
            }
        }

		/// <summary>
		/// Act code for a scroll.
		/// </summary>
		public static void Scroll(Game g, int index)
        {
            Thing thing = g.Things[index];
            byte color = g.GetColorAt(thing.Location);

            color++;
            if (color > 0x0F)
            {
                color = 0x09;
            }
            g.SetColorAt(thing.Location, color);
            g.UpdateAt(thing.Location);
        }

		/// <summary>
		/// Act code for a centipede segment.
		/// </summary>
		public static void Segment(Game g, int index)
        {
            Thing thing = g.Things[index];

            if (thing.Leader == -1)
            {
                thing.Leader = -2;
            }
            else if (thing.Leader == -2)
            {
                g.SetElementAt(thing.Location, g.Elements[ElementID.Head]);
                g.UpdateAt(thing.Location);
            }
        }

		/// <summary>
		/// Act code for a shark.
		/// </summary>
		public static void Shark(Game g, int index)
        {
            Thing thing = g.Things[index];
            Vector vector = new Vector();

            if (thing.P1 >= g.RandomNumber(10))
            {
                vector.CopyFrom(g.Seek(thing.Location));
            }
            else
            {
                vector.CopyFrom(g.Rnd());
            }

            Location target = thing.Location.Add(vector);
            Element element = g.GetElementAt(target);

            switch (element.Id)
            {
                case ElementID.Water:
                    g.MoveThing(index, target);
                    break;
                case ElementID.Player:
                    g.Attack(index, target);
                    break;
            }
        }

		/// <summary>
		/// Act code for a slime.
		/// </summary>
		public static void Slime(Game g, int index)
        {
            Thing thing = g.Things[index];

            if (thing.P1 >= thing.P2)
            {
                int spawnCount = 0;
                byte color = g.GetColorAt(thing.Location);
                Element slimeElement = g.Elements[ElementID.Slime];
                Tile slimeTrailTile = new Tile(g.Elements.IndexOf(ElementID.Breakable), color);
                Location source = thing.Location;
                thing.P1 = 0;

                for (int i = 0; i < 4; i++)
                {
                    Location target = source.Add(g.Parameters.CardinalVectors[i]);
                    if (g.GetElementAt(target).Floor)
                    {
                        if (spawnCount == 0)
                        {
                            g.MoveThing(index, target);
                            g.SetTileAt(source, slimeTrailTile);
                            g.UpdateAt(source);
                        }
                        else
                        {
                            g.SpawnThing(target, slimeElement, color, slimeElement.Cycle, null);
                            g.Things[g.Things.Count - 1].P2 = thing.P2;
                        }
                        spawnCount++;
                    }
                }

                if (spawnCount == 0)
                {
                    g.RemoveThing(index);
                    g.SetTileAt(source, slimeTrailTile);
                    g.UpdateAt(source);
                }
            }
            else
            {
                thing.P1++;
            }
        }

		/// <summary>
		/// Act code for a spider.
		/// </summary>
		public static void Spider(Game g, int index)
        {
            Thing thing = g.Things[index];
            Vector vector = new Vector();

            if (thing.P1 <= g.RandomNumber(10))
            {
                vector.CopyFrom(g.Rnd());
            }
            else
            {
                vector.CopyFrom(g.Seek(thing.Location));
            }
            if (!SpiderMove(g, index, vector))
            {
                int i = (g.RandomNumber(2) << 1) - 1;
                if (!SpiderMove(g, index, new Vector((Int16)(vector.Y * i), (Int16)(vector.X * i))))
                {
                    if (!SpiderMove(g, index, new Vector((Int16)(-vector.Y * i), (Int16)(-vector.X * i))))
                    {
                        SpiderMove(g, index, vector.Opposite());
                    }
                }
            }
        }

		/// <summary>
		/// Move the spider and attack the player if necessary.
		/// </summary>
		private static bool SpiderMove(Game g, int index, Vector vector)
        {
            bool result = false;
            Thing thing = g.Things[index];
            Location target = thing.Location.Add(vector);
            Element element = g.GetElementAt(target);

            if (element.Id == ElementID.Web)
            {
                g.MoveThing(index, target);
                result = true;
            }
            else if (element.Id == ElementID.Player)
            {
                g.Attack(index, target);
                result = true;
            }
            return result;
        }

		/// <summary>
		/// Act code for a spinning gun.
		/// </summary>
		public static void SpinningGun(Game g, int index)
        {
            Thing thing = g.Things[index];
            Thing player = g.Things[0];
            Element projectileElement;
            g.UpdateAt(thing.Location);

            if (thing.P2 >= 0x80)
                projectileElement = g.Elements[ElementID.Star];
            else
                projectileElement = g.Elements[ElementID.Bullet];

            if ((thing.P2 & 0x7F) > g.RandomNumber(9))
            {
                bool success = false;

                if (thing.P1 >= g.RandomNumber(9))
                {
                    if (g.Difference(thing.Location.X, player.Location.X) <= 2)
                    {
                        success = g.SpawnProjectile(projectileElement, thing.Location, new Vector(0, (Int16)g.Polarity(player.Location.Y - thing.Location.Y)), true);
                    }
                    if ((!success) && (g.Difference(thing.Location.Y, player.Location.Y) <= 2))
                    {
                        success = g.SpawnProjectile(projectileElement, thing.Location, new Vector((Int16)g.Polarity(player.Location.X - thing.Location.X), 0), true);
                    }
                }
                else
                {
                    success = g.SpawnProjectile(projectileElement, thing.Location, g.Rnd(), true);
                }
            }
        }

		/// <summary>
		/// Act code for a star.
		/// </summary>
		public static void Star(Game g, int index)
        {
            Thing thing = g.Things[index];

            thing.P2--;

            if (thing.P2 > 0)
            {
                if (thing.P2 % 2 == 0)
                {
                    thing.Vector.CopyFrom(g.Seek(thing.Location));
                    Location target = thing.Location.Add(thing.Vector);
                    Element targetElement = g.GetElementAt(target);

                    if ((targetElement.Id == ElementID.Player) || (targetElement.Id == ElementID.Breakable))
                    {
                        g.Attack(index, target);
                    }
                    else
                    {
                        if (!targetElement.Floor)
                        {
                            g.Push(target, thing.Vector);
                            targetElement = g.GetElementAt(target);
                        }
                        if ((targetElement.Floor) || (targetElement.Id == ElementID.Water))
                        {
                            g.MoveThing(index, target);
                        }
                    }
                }
                else
                {
                    g.UpdateAt(thing.Location);
                }
            }
            else
            {
                g.RemoveThing(index);
            }
        }

		/// <summary>
		/// Act code for a stone of power.
		/// </summary>
		public static void Stone(Game g, int index)
        {
            Thing thing = g.Things[index];
            g.SetColorAt(thing.Location, (byte)((g.GetColorAt(thing.Location) & 0x70) + g.RandomNumber(7) + 9));
            g.UpdateAt(thing.Location);
        }

		/// <summary>
		/// Act code for a tiger.
		/// </summary>
		public static void Tiger(Game g, int index)
        {
            Thing thing = g.Things[index];
            Thing player = g.Things[0];
            Element projectileElement;

            if (thing.P2 >= 0x80)
                projectileElement = g.Elements[ElementID.Star];
            else
                projectileElement = g.Elements[ElementID.Bullet];

            if ((thing.P2 % 0x80) >= g.RandomNumber(10) * 3)
            {
                bool success = false;
                if (g.Difference(thing.Location.X, player.Location.X) <= 2)
                {
                    success = g.SpawnProjectile(projectileElement, thing.Location, new Vector(0, (Int16)g.Polarity(player.Location.Y - thing.Location.Y)), true);
                }
                if ((!success) && (g.Difference(thing.Location.Y, player.Location.Y) <= 2))
                {
                    success = g.SpawnProjectile(projectileElement, thing.Location, new Vector((Int16)g.Polarity(player.Location.X - thing.Location.X), 0), true);
                }
            }
            Lion(g, index);
        }

		/// <summary>
		/// Act code for a transporter.
		/// </summary>
		public static void Transporter(Game g, int index)
        {
            g.UpdateAt(g.Things[index].Location);
        }
    }
}
